#include "praxis/ui/generic_property_editor_dialog.h"

#include <commctrl.h>

#include <map>

#include "res/resources.h"  // For dialog resource ID

namespace praxis {
struct CGenericPropertyEditorDialog::Impl {
  // Constructor parameters
  HWND h_parent_;
  json& data_;          // Reference to the external data
  const json& schema_;  // Const reference to the schema
  std::wstring title_;

  // Internal state
  HWND h_dlg_ = NULL;
  std::map<int, std::string> control_id_to_json_key_;

  Impl(HWND h_parent, json& data, const json& schema, const std::wstring& title)
      : h_parent_(h_parent), data_(data), schema_(schema), title_(title) {}

  // Dialog procedure and handlers
  static INT_PTR CALLBACK DlgProc(HWND h_dlg, UINT message, WPARAM w_param,
                                  LPARAM l_param);
  INT_PTR RealDlgProc(HWND h_dlg, UINT message, WPARAM w_param, LPARAM l_param);
  void OnInitDialog();
  void OnOK();
};

CGenericPropertyEditorDialog::CGenericPropertyEditorDialog(
    HWND h_parent, json& data, const json& schema, const std::wstring& title)
    : impl_(std::make_unique<Impl>(h_parent, data, schema, title)) {}

CGenericPropertyEditorDialog::~CGenericPropertyEditorDialog() = default;

INT_PTR CGenericPropertyEditorDialog::DoModal() {
  // The DialogBoxParam function will call our static DlgProc.
  // We pass 'this->impl_.get()' as the creation parameter (l_param).
  return DialogBoxParam(GetModuleHandle(NULL),
                        MAKEINTRESOURCE(IDD_GENERIC_PROPS), impl_->h_parent_,
                        Impl::DlgProc, (LPARAM)impl_.get());
}

INT_PTR CALLBACK CGenericPropertyEditorDialog::Impl::DlgProc(HWND h_dlg,
                                                             UINT message,
                                                             WPARAM w_param,
                                                             LPARAM l_param) {
  Impl* p_impl = nullptr;
  if (message == WM_INITDIALOG) {
    // Retrieve the Impl pointer we passed in DoModal and store it.
    p_impl = reinterpret_cast<Impl*>(l_param);
    SetWindowLongPtr(h_dlg, DWLP_USER, (LONG_PTR)p_impl);
    p_impl->h_dlg_ = h_dlg;
  } else {
    // Retrieve the stored Impl pointer for other messages.
    p_impl = reinterpret_cast<Impl*>(GetWindowLongPtr(h_dlg, DWLP_USER));
  }

  // If we have a valid pointer, delegate to the member function.
  return p_impl ? p_impl->RealDlgProc(h_dlg, message, w_param, l_param) : FALSE;
}

INT_PTR CGenericPropertyEditorDialog::Impl::RealDlgProc(HWND h_dlg,
                                                        UINT message,
                                                        WPARAM w_param,
                                                        LPARAM /*l_param*/) {
  switch (message) {
    case WM_INITDIALOG:
      OnInitDialog();
      return TRUE;
    case WM_COMMAND:
      switch (LOWORD(w_param)) {
        case IDOK:
          OnOK();
          EndDialog(h_dlg, IDOK);
          break;
        case IDCANCEL:
          EndDialog(h_dlg, IDCANCEL);
          break;
      }
      return TRUE;
  }
  return FALSE;
}

void CGenericPropertyEditorDialog::Impl::OnInitDialog() {
  SetWindowTextW(h_dlg_, title_.c_str());
  HFONT h_font = (HFONT)GetStockObject(DEFAULT_GUI_FONT);

  const int start_y = 10;
  const int y_step = 25;
  const int x_label = 10;
  const int x_edit = 120;
  const int w_label = 100;
  const int w_edit = 120;
  const int h_ctrl = 14;

  int current_y = start_y;
  int control_id_counter = IDC_PROP_FIRST_CTRL;

  if (!schema_.contains("properties")) return;

  for (auto it = schema_["properties"].begin();
       it != schema_["properties"].end(); ++it) {
    const std::string& key = it.key();
    const json& prop_schema = it.value();

    std::wstring label_text = ToWString(prop_schema.value("description", key));
    std::string unit = prop_schema.value("x-unit", "");
    if (!unit.empty()) {
      label_text += L" (" + ToWString(unit) + L")";
    }

    // Create Label
    HWND h_label = CreateWindowW(
        WC_STATICW, label_text.c_str(), WS_CHILD | WS_VISIBLE | SS_RIGHT,
        x_label, current_y + 2, w_label, h_ctrl, h_dlg_, NULL, NULL, NULL);
    SendMessage(h_label, WM_SETFONT, (WPARAM)h_font, TRUE);

    // Create Edit Control
    HWND h_edit = CreateWindowW(
        WC_EDITW, L"", WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
        x_edit, current_y, w_edit, h_ctrl, h_dlg_,
        (HMENU)(UINT_PTR)control_id_counter, NULL, NULL);
    SendMessage(h_edit, WM_SETFONT, (WPARAM)h_font, TRUE);

    // Set initial value from data
    if (data_.contains(key)) {
      std::string val_str;
      if (data_[key].is_string()) {
        val_str = data_[key].get<std::string>();
      } else {
        val_str = data_[key].dump();
      }
      SetWindowTextW(h_edit, ToWString(val_str).c_str());
    }

    control_id_to_json_key_[control_id_counter] = key;

    current_y += y_step;
    control_id_counter++;
  }
}

void CGenericPropertyEditorDialog::Impl::OnOK() {
  for (const auto& [id, key] : control_id_to_json_key_) {
    wchar_t buffer[256];
    GetDlgItemTextW(h_dlg_, id, buffer, 256);
    std::string value_str = FromWString(buffer);

    // Attempt to parse back to the correct type based on schema
    const auto& prop_schema = schema_["properties"][key];
    std::string type = prop_schema.value("type", "string");
    try {
      if (type == "number")
        data_[key] = std::stod(value_str);
      else if (type == "integer")
        data_[key] = std::stoi(value_str);
      else if (type == "boolean")
        data_[key] = (value_str == "true" || value_str == "1");
      else
        data_[key] = value_str;
    } catch (...) {
      // Conversion failed, store as string or handle error
      data_[key] = value_str;
    }
  }
}
}  // namespace praxis